// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-12-08

package managers

import (
	"context"
	"fmt"
	"gitee.com/go-libs/log"
	"gitee.com/go-libs/process"
	"gitee.com/gomq/sr"
	"gomq/app/models"
	"sync"
)

type (
	// ServerManager is a component used to operate servlet settings for
	// rabbitmq, rocketmq and so on.
	ServerManager interface {
		// Cluster return the active cluster model. Only one cluster
		// allowed to be activated.
		Cluster() *models.Cluster

		// Flush running consumers of the servlet.
		Flush()

		// Keep return true if current cluster is completely consistent
		// with previous.
		Keep(cluster *models.Cluster) bool

		// Process return the virtual process like os.
		Process() process.Process

		// Publish publishes a payload to topic of the servlet like
		// rabbitmq, rocketmq and so on.
		Publish(msg *sr.Payload) (err error)

		// Servlet returns the servlet instance.
		Servlet() sr.Servlet
	}

	server struct {
		bucket             sr.Bucket[*sr.Payload]
		cluster            *models.Cluster
		consumerNames      map[string]int
		consumerTimestamps map[int]int64
		manager            Manager
		mu                 *sync.RWMutex
		name               string
		process            process.Process
		servlet            sr.Servlet
	}
)

// + Implements

func (o *server) Cluster() *models.Cluster          { return o.cluster }
func (o *server) Flush()                            { o.flush() }
func (o *server) Keep(cluster *models.Cluster) bool { return o.isKeep(cluster) }
func (o *server) Process() process.Process          { return o.process }
func (o *server) Publish(msg *sr.Payload) error     { return o.bucket.Add(msg) }
func (o *server) Servlet() sr.Servlet               { return o.servlet }

// + Process

func (o *server) Run(ctx context.Context) (err error) {
	// Flush all consumers before startup.
	o.flush()

	// Listen context channel message.
	for {
		select {
		case <-ctx.Done():
			return
		}
	}
}

// + Events

func (o *server) OnConsuming(subscription sr.Subscription, message *sr.Message) (err error) {
	csm := NewConsuming(o.manager, o, subscription, message)
	err = csm.Do()
	csm.Release()
	return
}

func (o *server) OnFlush(subscription sr.Subscription, name string, updated bool) {
	var (
		c  sr.Consumer
		p  process.Process
		ok bool
	)

	// Servlet consumer found in child processes.
	if p, ok = o.servlet.Process().GetChild(name); ok {
		// Do nothing if current settings are completely consistent with
		// previous.
		if !updated {
			return
		}

		// Stop previous consumer.
		log.Infof(`[%s] stop previous servlet consumer: name="%s", uid="%s"`, o.name, name, p.GetUid())
		p.StopWait()
	}

	// Creates a new servlet consumer.
	log.Infof(`[%s] create servlet consumer: name="%s"`, o.name, name)
	c = o.servlet.Consumer(name, subscription, o.OnConsuming)
	o.servlet.Process().Add(c.Process())
}

func (o *server) OnPublish(list []*sr.Payload) {
	for _, v := range list {
		p := NewPublishing(o, v)
		p.Do()
		p.Release()
		v.Release()
	}
}

// + Access

func (o *server) flush() {
	var (
		currNames                = make(map[string]int)
		currTimestamps           = make(map[int]int64)
		lastNames                map[string]int
		lastTimestamps           map[int]int64
		countTask, countConsumer int
	)

	log.Infof(`[%s] flush servlet consumers begin`, o.name)

	defer func() {
		countTask, countConsumer = len(currTimestamps), len(currNames)
		log.Infof(`[%s] flush servlet consumers finish: task="%d", consumer="%d"`, o.name, countTask, countConsumer)
	}()

	// Read last names and timestamps in order to stop or flush previous
	// servlet consumers.
	o.mu.RLock()
	lastNames = o.consumerNames
	lastTimestamps = o.consumerTimestamps
	o.mu.RUnlock()

	// Iterate all enabled tasks.
	for _, task := range o.manager.MemoryManager().GetTaskModels() {
		subscription, has := o.manager.MemoryManager().GetSubscription(task.Id)

		// Skip if subscription not in memory.
		if !has {
			continue
		}

		// Init current state.
		updated := false
		timestamp := task.UpdatedAt.Time().Unix()
		currTimestamps[task.Id] = timestamp

		// Update last updated timestamp.
		if t, ok := lastTimestamps[task.Id]; !ok || t != timestamp {
			updated = true
		}

		// Iterate with subscription task threads.
		for thread := 0; thread < subscription.GetThreads(); thread++ {
			name := fmt.Sprintf(`consumer-%d-%d`, task.Id, thread)
			currNames[name] = task.Id

			// Flush in goroutine.
			go o.OnFlush(subscription, name, updated)
		}
	}

	// Compare current names and timestamps with last.
	for name := range lastNames {
		if _, ok := currNames[name]; ok {
			continue
		}

		// Send stop signal for last consumer name not in current.
		if p, ok := o.servlet.Process().GetChild(name); ok {
			p.Stop()
		}
	}

	// Reset latest consumer names and timestamps for next to do.
	o.mu.Lock()
	o.consumerNames = currNames
	o.consumerTimestamps = currTimestamps
	o.mu.Unlock()

}

func (o *server) isKeep(cluster *models.Cluster) bool {
	return cluster.Id == o.cluster.Id &&
		cluster.UpdatedAt.Time().Unix() == o.cluster.UpdatedAt.Time().Unix()
}

// + Constructor

func (o *server) init() *server {
	o.name = "server-manager"
	o.bucket = sr.NewBucket[*sr.Payload](o.name, o.OnPublish)
	o.consumerNames = make(map[string]int)
	o.consumerTimestamps = make(map[int]int64)
	o.mu = new(sync.RWMutex)
	o.process = process.New(o.name, o)
	o.process.Add(o.servlet.Process())
	return o
}
